package opensearchext

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	opensearchapi "github.com/opensearch-project/opensearch-go/opensearchapi"
)

// ----- API Definition -------------------------------------------------------

type MapRoleUserRequest struct {
	RoleName     string   `json:"role_name,omitempty"`
	Users        []string `json:"users,omitempty"`
	BackendRoles []string `json:"backend_roles,omitempty"`
	Hosts        []string `json:"hosts,omitempty"`

	IfPrimaryTerm       *int
	IfSeqNo             *int
	Refresh             string
	Routing             string
	Timeout             time.Duration
	Version             *int
	VersionType         string
	WaitForActiveShards string

	Pretty     bool
	Human      bool
	ErrorTrace bool
	FilterPath []string

	Header http.Header

	ctx context.Context
}

func (m MapRoleUserRequest) MarshalJSON() ([]byte, error) {
	mruMap := make(map[string]interface{})
	if len(m.Users) > 0 {
		mruMap["users"] = m.Users
	}
	if len(m.BackendRoles) > 0 {
		mruMap["backend_roles"] = m.BackendRoles
	}
	if len(m.Hosts) > 0 {
		mruMap["hosts"] = m.Hosts
	}

	return json.Marshal(mruMap)
}

func (m MapRoleUserRequest) Do(ctx context.Context, transport opensearchapi.Transport) (*opensearchapi.Response, error) {
	var (
		method string
		path   strings.Builder
		params map[string]string
	)

	method = "PUT"
	//_plugins/_security/api/rolesmapping/<rolename>
	path.Grow(len("/_plugins/_security/api/rolesmapping") + 1 + len(m.RoleName))
	path.WriteString("/_plugins/_security/api/rolesmapping/")
	path.WriteString(m.RoleName)

	params = make(map[string]string)

	if m.IfPrimaryTerm != nil {
		params["if_primary_term"] = strconv.FormatInt(int64(*m.IfPrimaryTerm), 10)
	}

	if m.IfSeqNo != nil {
		params["if_seq_no"] = strconv.FormatInt(int64(*m.IfSeqNo), 10)
	}

	if m.Refresh != "" {
		params["refresh"] = m.Refresh
	}

	if m.Routing != "" {
		params["routing"] = m.Routing
	}

	if m.Timeout != 0 {
		params["timeout"] = formatDuration(m.Timeout)
	}

	if m.Version != nil {
		params["version"] = strconv.FormatInt(int64(*m.Version), 10)
	}

	if m.VersionType != "" {
		params["version_type"] = m.VersionType
	}

	if m.WaitForActiveShards != "" {
		params["wait_for_active_shards"] = m.WaitForActiveShards
	}

	if m.Pretty {
		params["pretty"] = "true"
	}

	if m.Human {
		params["human"] = "true"
	}

	if m.ErrorTrace {
		params["error_trace"] = "true"
	}

	if len(m.FilterPath) > 0 {
		params["filter_path"] = strings.Join(m.FilterPath, ",")
	}

	reqBody, err := json.Marshal(m)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal structure: %s", err)
	}
	body := strings.NewReader(string(reqBody))

	req, err := newRequest(method, path.String(), body)
	if err != nil {
		return nil, fmt.Errorf("failed to form request: %s", err)
	}

	if len(params) > 0 {
		q := req.URL.Query()
		for k, v := range params {
			q.Set(k, v)
		}
		req.URL.RawQuery = q.Encode()
	}
	if body != nil {
		req.Header[headerContentType] = []string{headerContentTypeJSON}
	}

	if len(m.Header) > 0 {
		if len(req.Header) == 0 {
			req.Header = m.Header
		} else {
			for k, vv := range m.Header {
				for _, v := range vv {
					req.Header.Add(k, v)
				}
			}
		}
	}

	if ctx != nil {
		req = req.WithContext(ctx)
	}

	res, err := transport.Perform(req)
	if err != nil {
		return nil, fmt.Errorf("failed to perform request: %s", err)
	}

	response := opensearchapi.Response{
		StatusCode: res.StatusCode,
		Body:       res.Body,
		Header:     res.Header,
	}

	return &response, nil
}
